package net.asgarli.abalone.model;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;

import com.google.common.collect.Maps;

public class Vault implements Marshallable {
    private static final long serialVersionUID = 1L;

    private VersionInfo versionInfo;

    private EncryptionInfo encryptionInfo;

    private byte[] encryptedUnitPreviews;

    private Map<Long, byte[]> encryptedUnits = Maps.newHashMap();

    @Override
    public byte[] writeToBytes() throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);

        byte[] vInfoInBytes = versionInfo.writeToBytes();
        dos.writeInt(vInfoInBytes.length);
        dos.write(vInfoInBytes);

        byte[] encInfoInBytes = encryptionInfo.writeToBytes();
        dos.writeInt(encInfoInBytes.length);
        dos.write(encInfoInBytes);

        dos.writeInt(encryptedUnits.size());
        for (Long encUnitId : encryptedUnits.keySet()) {
            dos.writeLong(encUnitId);
            byte[] encUnit = encryptedUnits.get(encUnitId);
            dos.writeInt(encUnit.length);
            dos.write(encUnit);
        }

        return bos.toByteArray();
    }

    @Override
    public void restoreFromBytes(byte[] bytes) throws IOException {
        DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes));

        int vInfoInBytesSize = in.readInt();
        byte[] vInfoInBytes = new byte[vInfoInBytesSize];
        in.read(vInfoInBytes);
        versionInfo = new VersionInfo();
        versionInfo.restoreFromBytes(vInfoInBytes);

        int encInfoInBytesSize = in.readInt();
        byte[] encInfoInBytes = new byte[encInfoInBytesSize];
        in.read(encInfoInBytes);
        encryptionInfo = new EncryptionInfo();
        encryptionInfo.restoreFromBytes(encInfoInBytes);
        
        int encryptedUnitsSize = in.readInt();
        encryptedUnits.clear();
        for (int i = 0; i < encryptedUnitsSize; i++) {
            Long encryptedUnitId = in.readLong();
            int encUnitSize = in.readInt();
            byte[] encUnit = new byte[encUnitSize];
            in.read(encUnit);
            
            encryptedUnits.put(encryptedUnitId, encUnit);
        }
    }

    //
    // ************************* GENERATED *************************
    // ************************* Getters and Setters ***************
    //
    public VersionInfo getVersionInfo() {
        return versionInfo;
    }

    public void setVersionInfo(VersionInfo versionInfo) {
        this.versionInfo = versionInfo;
    }

    public EncryptionInfo getEncryptionInfo() {
        return encryptionInfo;
    }

    public void setEncryptionInfo(EncryptionInfo encryptionInfo) {
        this.encryptionInfo = encryptionInfo;
    }

    public byte[] getEncryptedUnitPreviews() {
        return encryptedUnitPreviews;
    }

    public void setEncryptedUnitPreviews(byte[] encryptedUnitPreviews) {
        this.encryptedUnitPreviews = encryptedUnitPreviews;
    }

    public Map<Long, byte[]> getEncryptedUnits() {
        return encryptedUnits;
    }

    public void setEncryptedUnits(Map<Long, byte[]> encryptedUnits) {
        this.encryptedUnits = encryptedUnits;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(encryptedUnitPreviews);
        result = prime * result + ((encryptedUnits == null) ? 0 : encryptedUnits.hashCode());
        result = prime * result + ((encryptionInfo == null) ? 0 : encryptionInfo.hashCode());
        result = prime * result + ((versionInfo == null) ? 0 : versionInfo.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (!(obj instanceof Vault)) return false;
        Vault other = (Vault) obj;
        if (!Arrays.equals(encryptedUnitPreviews, other.encryptedUnitPreviews)) return false;
        if (encryptedUnits == null) {
            if (other.encryptedUnits != null) return false;
        } else if (!encryptedUnits.equals(other.encryptedUnits)) return false;
        if (encryptionInfo == null) {
            if (other.encryptionInfo != null) return false;
        } else if (!encryptionInfo.equals(other.encryptionInfo)) return false;
        if (versionInfo == null) {
            if (other.versionInfo != null) return false;
        } else if (!versionInfo.equals(other.versionInfo)) return false;
        return true;
    }
}
